package com.pan.studentmaster;

import java.io.*;
import java.util.ArrayList;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import org.xmlpull.v1.XmlSerializer;

import android.content.Context;
import android.content.res.Resources.NotFoundException;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.PointF;
import android.os.Environment;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Xml;
import android.view.MotionEvent;
import android.view.View;

public class SVGPage extends View {

	public SVGPage(Context context) {
		super(context);
	}

	public SVGPage(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	public SVGPage(Context context, AttributeSet attrs, int defStyleAttr) {
		super(context, attrs, defStyleAttr);
	}
	
	public void reset() {
		_paths.clear();
		_bitmap = Bitmap.createBitmap(_width, _height, Bitmap.Config.ARGB_8888);
		_canvas = new Canvas(_bitmap);
		_canvas.drawARGB(0, 0, 0, 0);
	}
	
	public void load(int bookId, int pageId) {
		_bookId = bookId;
		_pageId = pageId;
		if(_width < 0 || _height <0) {
			// view size is not determined yet, so postpone the load
			return;
		}
		Cursor cursor = DB.instance().getBookInfo(bookId);
		_svgFile = cursor.getString(cursor.getColumnIndex(DB.BOOK._TITLE));
		_svgFile += cursor.getString(cursor.getColumnIndex(DB.BOOK._SUBTITLE));
		_svgFile = _svgFile.replaceAll(" ", "");
		_svgFile = _svgFile.toLowerCase();
		_svgFile = Environment.getExternalStorageDirectory().getAbsolutePath() + "/student/" + _svgFile;
		File dir = new File(_svgFile);
		if(!dir.exists()) {
			if(!dir.mkdir())
				Log.e("SVG", "Failed to create directory " + _svgFile);
		}
		_svgFile = _svgFile + "/p" + pageId + ".svg";
		Log.i("SVG", "open " + _svgFile);
		XmlPullParserFactory factory;
		try {
			factory = XmlPullParserFactory.newInstance();
			factory.setNamespaceAware(true);
			XmlPullParser xpp = factory.newPullParser();
			xpp.setInput(new FileInputStream(_svgFile), ENCODING);
			int eventType = xpp.getEventType();
			SVGPath path = null;
			while (eventType != XmlPullParser.END_DOCUMENT) {
				switch(eventType) {
				case XmlPullParser.START_TAG :
					if(xpp.getName().equals(TAG_SIZE)) {
						int width = Integer.valueOf(xpp.getAttributeValue(null, ATTRIBUTE_X));
						int height = Integer.valueOf(xpp.getAttributeValue(null, ATTRIBUTE_Y));
						if(_width != width || _height != height) {
							// TO DO: view size is different from what is recorded, need to zoom
							Log.i("SVG", "View size is different from file");
						}
					}
					else if(xpp.getName().equals(TAG_PATH)) {
						int pen = Integer.valueOf(xpp.getAttributeValue(null, ATTRIBUTE_PEN));
						path = new SVGPath(pen);
						_paths.add(path);
					}
					else if(xpp.getName().equals(TAG_POINT)) {
						float x = Float.valueOf(xpp.getAttributeValue(null, ATTRIBUTE_X));
						float y = Float.valueOf(xpp.getAttributeValue(null, ATTRIBUTE_Y));
						path.addPoint(x, y);
					}
					break;
				case XmlPullParser.END_TAG :
					if(xpp.getName().compareTo(TAG_PATH) == 0) {
						_canvas.drawPath(path, Stationary.getPaint(path._pen));
					}
					break;
				}
				eventType = xpp.next();
			}
		} catch (NotFoundException e) {
			Log.e("XML", "not found");
		} catch (XmlPullParserException e) {
			Log.e("XML", "parser error:" + e.getMessage());
		} catch (IOException e) {
			Log.e("XML", "IOException "+ e.getMessage());
		}
	}
	
	public void save() {
		if(_modified) {
			_modified = false;
			Log.i("SVG", "Writing " + _svgFile);
			XmlSerializer xml = Xml.newSerializer();
			OutputStream out = null;
			try {
				out = new BufferedOutputStream(new FileOutputStream(_svgFile));
				xml.setOutput(out, ENCODING);
				xml.startDocument(ENCODING, true);
				xml.startTag(null, TAG_SIZE);
				xml.attribute(null, ATTRIBUTE_X, String.valueOf(_bitmap.getWidth()));
				xml.attribute(null, ATTRIBUTE_Y, String.valueOf(_bitmap.getHeight()));
				xml.endTag(null, TAG_SIZE);
				for(SVGPath path: _paths)
					path.output(xml);
				xml.endDocument();
				xml.flush();
				out.close();
			} catch (IllegalArgumentException e) {
				Log.e("XML", "IllegalArgument:" + e.getMessage());
			} catch (FileNotFoundException e) {
				Log.e("XML", "FileNotFound:" + _svgFile + ":" + e.getMessage());
			} catch (IllegalStateException e) {
				Log.e("XML", "IllegalState:" + e.getMessage());
			} catch (IOException e) {
				Log.e("XML", "IOException:" + e.getMessage());
			}
		}
	}
	
	@Override
	public void onDraw(Canvas canvas) {
		super.onDraw(canvas);
		canvas.drawColor(0x000000);
		canvas.drawBitmap(_bitmap, 0, 0, null);
		if(_currentPath != null)
			canvas.drawPath(_currentPath, Stationary.getCurrentPaint());
	}
	
	@Override
	public void onSizeChanged(int w, int h, int oldW, int oldH) {
		Log.i("SVG", "resize to " + w + "," + h);
		_width = w;
		_height = h;
		reset();
		if(_bookId >= 0 && _pageId >= 0)
			load(_bookId, _pageId);
	}
	
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		int action = event.getAction() & MotionEvent.ACTION_MASK;
		switch(action) {
		case MotionEvent.ACTION_DOWN :
			_currentPath = new SVGPath(Stationary.getCurrentStationary());
			_modified = true;
			_currentPath.addPoint(event.getX(), event.getY());
			invalidate();
			break;
		case MotionEvent.ACTION_UP :
			_currentPath.addPoint(event.getX(), event.getY());
			if(Stationary.getCurrentStationary() == Stationary.ERASER * Stationary.M + Stationary.E_SUPER_PENCIL ||
				Stationary.getCurrentStationary() == Stationary.ERASER * Stationary.M + Stationary.E_SUPER_OMNI) {
				eraseStrokes();
			}
			else {
				_canvas.drawPath(_currentPath, Stationary.getCurrentPaint());
				_paths.add(_currentPath);
			}
			_currentPath = null;
			invalidate();
			Log.i("SVG", "Path finish");
			break;
		case MotionEvent.ACTION_MOVE :
			_currentPath.addPoint(event.getX(), event.getY());
			invalidate();
			break;
		}
		// No more handles, so return true to stop 
		return true;
	}
	
	public void cancelCurrentPath() {
		_currentPath = null;
	}
	
	// private
	
	static private final float		TOUCH_TOLERANCE = 4;
	// for SVG image file Western encoding should be enough
	static private final String		ENCODING = "UTF-8";
	static private final String		TAG_PATH = "PATH";
	static private final String		TAG_POINT = "POINT";
	static private final String		TAG_SIZE = "SIZE";
	static private final String		ATTRIBUTE_PEN = "PEN";
	static private final String		ATTRIBUTE_X = "X";
	static private final String		ATTRIBUTE_Y = "Y";
	private SVGPath					_currentPath = null;
	private Bitmap					_bitmap = null;
	private Canvas					_canvas = null;
	private ArrayList<SVGPath>		_paths = new ArrayList<SVGPath>();
	private String					_svgFile = new String();
	private int						_bookId = -1;
	private int						_pageId = -1;
	private int						_width = -1;
	private int						_height = -1;
	private boolean					_modified = false;
	
	private void eraseStrokes() {
		boolean modified = false;
		int mainStationary = Stationary.getCurrentStationary() / Stationary.M;
		if(mainStationary != Stationary.ERASER)
			return;
		int subStationary = Stationary.getCurrentStationary() % Stationary.M;
		Log.i("SVG", "Super eraser " + subStationary);
		// reversed iterator, so don't need to worry about modified _paths
		for(int i = _paths.size() - 1; i >= 0; --i) {
			Log.i("SVG", "Checking path " + i);
			SVGPath path = _paths.get(i);
			if(path._pen / Stationary.M == Stationary.ERASER)
				continue;
			if(subStationary == Stationary.E_SUPER_PENCIL && path._pen / Stationary.M != Stationary.PENCIL)
				continue;
			if(path.intersect(_currentPath)) {
				Log.i("SVG", "remove");
				_paths.remove(i);
				modified = true;
			}
		}
		if(modified) {
			_bitmap = Bitmap.createBitmap(_width, _height, Bitmap.Config.ARGB_8888);
			_canvas = new Canvas(_bitmap);
			_canvas.drawARGB(0, 0, 0, 0);
			for(SVGPath path: _paths)
				_canvas.drawPath(path, Stationary.getPaint(path._pen));
		}
	}
	
	private class SVGPath extends Path {
		
		public SVGPath(int pen) {
			super();
			_pen = pen;
		}
		
		public void output(XmlSerializer xml) throws IllegalArgumentException, IllegalStateException, IOException {
			xml.startTag(null, TAG_PATH);
			xml.attribute(null, ATTRIBUTE_PEN, String.valueOf(_pen));
			for(PointF p: _points) {
				xml.startTag(null, TAG_POINT);
				xml.attribute(null, ATTRIBUTE_X, String.valueOf(p.x));
				xml.attribute(null, ATTRIBUTE_Y, String.valueOf(p.y));
				xml.endTag(null, TAG_POINT);
			}
			xml.endTag(null, TAG_PATH);
		}
		
		public void addPoint(float px, float py) {
			if(_points.size() == 0) {
				moveTo(px, py);
				_px = px;
				_py = py;
				if(_px < _minX)
					_minX = _px;
				if(_px > _maxX)
					_maxX = _px;
				if(_py < _minY)
					_minY = _py;
				if(_py > _maxY)
					_maxY = _py;
				_points.add(new PointF(px, py));
			}
			else {
				float dx = Math.abs(px - _px);
				float dy = Math.abs(py - _py);
				if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
					quadTo(_px, _py, (px + _px)/2, (py + _py)/2);
					_px = px;
					_py = py;
					if(_px < _minX)
						_minX = _px;
					if(_px > _maxX)
						_maxX = _px;
					if(_py < _minY)
						_minY = _py;
					if(_py > _maxY)
						_maxY = _py;
					_points.add(new PointF(px, py));
				}
			}
		}
		
		// determine if two paths intersect. this is used to erase a stroke
		private boolean intersect(SVGPath that) {
			// first check if two rectangle area intersects, this can reduce numbers of path to compare
			if(_minX > that._maxX || _minY > that._maxY || _maxX < that._minX || _maxY < that._minY)
				return false;
			for(PointF p1 : _points) {
				for(PointF p2 : that._points) {
					float dx = p1.x - p2.x;
					float dy = p1.y - p2.y;
					if(Math.abs(dx) < Stationary.STROKE_ERASER && Math.abs(dy) < Stationary.STROKE_ERASER)
						return true;
				}
			}
			return false;
		}
		
		private ArrayList<PointF>	_points = new ArrayList<PointF>();
		private final int			_pen;
		private float				_px;
		private float				_py;
		private float				_minX = Float.MAX_VALUE;
		private float				_minY = Float.MAX_VALUE;
		private float				_maxX = 0f;
		private float				_maxY = 0f;
	}
}
